#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kprobes.h>
#include <linux/uaccess.h>
#include <linux/i2c.h>
#include <linux/sched.h>
#include <asm/ptrace.h>
#include <linux/workqueue.h>
#include <drm/drmP.h>
#include <drm/drm_connector.h>
#include <drm/drm_drv.h>
#include <drm/drm_crtc_helper.h>


#include <../drivers/gpu/drm/loongson/loongson_drv.h>

//#include "asm/mach-loongson/loongson-pch.h"



rwlock_t *pls7a_rwlock;
unsigned long *pls7a_rwflags;

#define ls7a_rwlock *pls7a_rwlock
#define ls7a_rwflags *pls7a_rwflags

#define DLEF "drm_load_edid_firmware"
#define OPE "output_poll_execute"
#define DMGC "drm_mode_getconnector"
#define DHPSCM "drm_helper_probe_single_connector_modes"
#define LCD "loongson_connector_detect"
#define LOONGSON_GET_MODES "loongson_vga_get_modes"
#define LCMS "loongson_crtc_mode_set"
#define DAEM "drm_add_edid_modes"


unsigned long dc_base = 0;

struct loongson_connector *gls_connector = NULL;
struct drm_device *pgdrm_device = NULL;
struct drm_mode_get_connector *pgout_resp = NULL;

static int entry_handler_ope(struct kretprobe_instance *ri, struct pt_regs *regs)
{

#ifdef CONFIG_MIPS

  pr_info("<%s> handler_pre: current_task.name:%s, p->addr = 0x%p, status = 0x%lx\n",
  ri->rp->kp.symbol_name, current->comm, ri->rp->kp.addr, regs->cp0_status);

#endif

  return 0;
}

static int ret_handler_ope(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  unsigned long retval = (unsigned long)regs_return_value(regs);

  pr_info("%s return %ld\n", ri->rp->kp.symbol_name, retval);
  pr_info("-----------------------------------------\n\n");

  return 0;
}

static int entry_handler_dmgc(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  struct drm_device *pdrm_device = NULL;
  struct drm_connector *pdrm_connector = NULL;
  struct loongson_connector *ls_connector = NULL;
  struct drm_encoder *pdrm_encoder = NULL;
  struct loongson_encoder *ls_encoder = NULL;
  struct drm_mode_get_connector *pout_resp = NULL;

  pr_info("\n----------------------\n<%s>:\n", __func__);

#ifdef CONFIG_MIPS

  pdrm_device = (struct drm_device *)(regs->regs[4]);
  pgdrm_device = pdrm_device;
  pout_resp = (struct drm_mode_get_connector *)(regs->regs[5]);
  pgout_resp = pout_resp;

  pdrm_connector = drm_connector_lookup(pdrm_device, pout_resp->connector_id);
  ls_connector = to_loongson_connector(pdrm_connector);

  gls_connector = ls_connector;

  if (ls_connector != NULL && ls_connector->connector_id == 1) {
    //pdrm_encoder = drm_encoder_find(pdrm_device, pout_resp->encoder_id);
    pdrm_encoder = pdrm_connector->state ? pdrm_connector->state->best_encoder : pdrm_connector->encoder;
    if (pdrm_encoder == NULL) {
      pr_err("ls: pdrm_encoder get failed!\n");

      list_for_each_entry(pdrm_encoder, &pdrm_device->mode_config.encoder_list, head) {
        pr_info("pdrm_encoder->name:%s, index:%d, id:%d\n", pdrm_encoder->name, pdrm_encoder->index, pdrm_encoder->base.id);
        if (pdrm_encoder->index == 1) {
          break;
        }
      }
    }

    ls_encoder = to_loongson_encoder(pdrm_encoder);
    if (ls_encoder == NULL) {
      pr_err("ls: ls_encoder get failed!\n");
    }

  }


  pr_info("pdrm_connector:%p, connector_id:%d, encoder_id:%d, count_modes:%d, pout_resp:%p\n", pdrm_connector, pout_resp->connector_id, pout_resp->encoder_id, pout_resp->count_modes, pout_resp);
  pr_info("driver_features:%d, DRIVER_MODESET:%d\n", pdrm_device->driver->driver_features, DRIVER_MODESET);
  pr_info("connector->override_edid:%d\n", pdrm_connector->override_edid);
  pr_info("ls_connector->connector_id:%d\n", ls_connector->connector_id);
  if (ls_connector->connector_id == 1)
    pr_info("ls_encoder->encoder_id:%d\n", ls_encoder->encoder_id);
  pr_info("pdrm_device->mode_config.poll_running:%d\n", pdrm_device->mode_config.poll_running);
  pr_info("pdrm_device->mode_config.poll_enabled:%d\n", pdrm_device->mode_config.poll_enabled);
  pr_info("drm_connector: force:%d, DRM_FORCE_ON:%d\n", pdrm_connector->force, DRM_FORCE_ON);


  pr_info("pdrm_device:%p\n", pdrm_device);
  pr_info("{%s} handler_pre: current_task.name:%s, p->addr = 0x%p, status = 0x%lx\n",
  ri->rp->kp.symbol_name, current->comm, ri->rp->kp.addr, regs->cp0_status);

#endif

	return 0;
}

static int ret_handler_dmgc(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  unsigned long retval = (unsigned long)regs_return_value(regs);

  pr_info("%s return %ld", ri->rp->kp.symbol_name, retval);
  if (gls_connector != NULL)
    pr_info(", connector_id:%d\n", gls_connector->connector_id);
  pr_info("-----------------------------------------\n\n");

  pgout_resp = NULL;

  return 0;
}

static int entry_handler_dhpscm(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  struct drm_connector *pdrm_connector = NULL;

#ifdef CONFIG_MIPS

  pdrm_connector = (struct drm_connector *)(regs->regs[4]);

  pr_info("pdrm_connector:%p\n", pdrm_connector);
  pr_info("<%s> handler_pre: current_task.name:%s, p->addr = 0x%p, status = 0x%lx\n",
  ri->rp->kp.symbol_name, current->comm, ri->rp->kp.addr, regs->cp0_status);

#endif

  return 0;
}

static int ret_handler_dhpscm(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  unsigned long retval = (unsigned long)regs_return_value(regs);

  pr_info("%s return %ld\n", ri->rp->kp.symbol_name, retval);
  pr_info("-----------------------------------------\n\n");

  return 0;
}

static int entry_handler_lcd(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  struct drm_connector *pdrm_connector = NULL;
  struct loongson_connector *ls_connector = NULL;


#ifdef CONFIG_MIPS

  pdrm_connector = (struct drm_connector *)(regs->regs[4]);
  ls_connector = to_loongson_connector(pdrm_connector);

  pr_info("<%s> handler_pre: current_task.name:%s, p->addr = 0x%p, status = 0x%lx\n",
    ri->rp->kp.symbol_name, current->comm, ri->rp->kp.addr, regs->cp0_status);
  pr_info("pdrm_connector:%p, connector_id:%d\n", pdrm_connector, ls_connector->connector_id);

#endif


  return 0;
}

static int ret_handler_lcd(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  unsigned long retval = (unsigned long)regs_return_value(regs);

  pr_info("%s return %ld\n", ri->rp->kp.symbol_name, retval);
  pr_info("-----------------------------------------\n\n");

  return 0;
}


static int entry_handler_loongson_get_modes(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  struct drm_connector *connector = NULL;
  struct loongson_connector *ls_connector = NULL;

  pr_info("ls: %s entry\n", ri->rp->kp.symbol_name);

#ifdef CONFIG_MIPS
  connector = (struct drm_connector *)(regs->regs[4]);

#else

#endif

  pr_info("ls: connector->index:%d\b", connector->index);

  ls_connector = to_loongson_connector(connector);
  if (ls_connector == NULL) {
    pr_err("ls: ls_connector get failed!\n");
    return -1;
  }
  gls_connector = ls_connector;

  pr_info("ls: connector_id:%d, i2c_id:%d\n", ls_connector->connector_id, ls_connector->vbios_connector->i2c_id);

  if (ls_connector->connector_id != 1)
    return 0;



  //pgout_resp = NULL;

  return 0;
}

static int ret_handler_loongson_get_modes(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  unsigned long retval = (unsigned long)regs_return_value(regs);

  pr_info("%s return %ld, connector_id:%d\n", ri->rp->kp.symbol_name, retval, gls_connector->connector_id);
  pr_info("-----------------------------------------\n\n");

  //gls_connector = NULL;

  return 0;
}

static int entry_handler_lcms(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  struct drm_crtc *crtc = NULL;
  struct loongson_crtc *loongson_crtc = NULL;
  struct drm_device *dev = NULL;
  struct loongson_drm_device *ldev = NULL;
  struct drm_display_mode *mode = NULL;
  //struct loongson_connector *ls_connector = NULL;
  unsigned int pix_freq;
  unsigned int depth;
  unsigned int hr, hss, hse, hfl;
  unsigned int vr, vss, vse, vfl;
  unsigned int crtc_id;
  int x, y = 0;


  pr_info("<%s>: entry\n", ri->rp->kp.symbol_name);

#ifdef CONFIG_MIPS
  crtc = (struct drm_crtc *)(regs->regs[4]);
  //pr_info("ls:%s---%d\n", __func__, __LINE__);
  mode = (struct drm_display_mode *)(regs->regs[5]);
  //pr_info("ls:%s---%d\n", __func__, __LINE__);
  x = (int)(regs->regs[7]);
  //pr_info("ls:%s---%d\n", __func__, __LINE__);
  //y = (int)(regs->regs[8]);
  //pr_info("ls:%s---%d\n", __func__, __LINE__);

#else

#endif

  loongson_crtc = to_loongson_crtc(crtc);

  dev = crtc->dev;
  ldev = dev->dev_private;
  dc_base = (unsigned long)(ldev->rmmio);

  pr_info("ls: crtc->name:%s, use_local_param:%d\n", crtc->name, loongson_crtc->vbios_crtc->use_local_param);
  pr_info("dc_base:0x%lx\n", dc_base);


  hr = mode->hdisplay;    hss = mode->hsync_start;
  hse = mode->hsync_end;  hfl = mode->htotal;
  vr = mode->vdisplay;    vss = mode->vsync_start;
  vse = mode->vsync_end;  vfl = mode->vtotal;
  depth = crtc->primary->fb->bits_per_pixel;
  pix_freq = mode->clock;
  crtc_id = loongson_crtc->crtc_id;


  pr_info("crtc_id:%d, hr = %d, hss = %d, hse = %d, hfl = %d, vr = %d, vss = %d," \
    "vsse = %d, vfl = %d, depth = %d, pix_freq = %d, x = %d, y = %d\n",
    crtc_id, hr, hss, hse, hfl, vr, vss, vse, vfl, depth,
    pix_freq, x, y);

  return 0;
}

static int ret_handler_lcms(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  unsigned long retval = (unsigned long)regs_return_value(regs);
  unsigned int cfg_reg_dvo0 = 0, cfg_reg_dvo1 = 0;
  unsigned int pitch_dvo0 = 0, pitch_dvo1 = 0;
  unsigned int dittab_lo_dvo0 = 0, dittab_lo_dvo1 = 0, dittab_hi_dvo0 = 0, dittab_hi_dvo1 = 0;
  unsigned int pancfg_dvo0 = 0, pancfg_dvo1 = 0, pantim_dvo0 = 0, pantim_dvo1 = 0;
  unsigned int dvo0_hdisplay = 0, dvo0_vdisplay = 0, dvo1_hdisplay = 0, dvo1_vdisplay = 0;
  unsigned int dvo0_hsync_reg = 0, dvo1_hsync_reg = 0, dvo0_vsync_reg = 0, dvo1_vsync_reg = 0;
  //unsigned long base;

  pr_info("%s return hook begin, return %ld\n", ri->rp->kp.symbol_name, retval);

  //base = 0x80000efe00001000;
  cfg_reg_dvo0 = ls_readl(dc_base + LS_FB_CFG_DVO0_REG);
  cfg_reg_dvo1 = ls_readl(dc_base + LS_FB_CFG_DVO1_REG);

  pitch_dvo0 = ls_readl(dc_base + LS_FB_STRI_DVO0_REG);
  pitch_dvo1 = ls_readl(dc_base + LS_FB_STRI_DVO1_REG);

  dittab_lo_dvo0 = ls_readl(dc_base + LS_FB_DITTAB_LO_DVO0_REG);
  dittab_hi_dvo0 = ls_readl(dc_base + LS_FB_DITTAB_HI_DVO0_REG);

  dittab_lo_dvo1 = ls_readl(dc_base + LS_FB_DITTAB_LO_DVO1_REG);
  dittab_hi_dvo1 = ls_readl(dc_base + LS_FB_DITTAB_HI_DVO1_REG);

  pancfg_dvo0 = ls_readl(dc_base + LS_FB_PANCFG_DVO0_REG);
  pancfg_dvo1 = ls_readl(dc_base + LS_FB_PANCFG_DVO1_REG);
  pantim_dvo0 = ls_readl(dc_base + LS_FB_PANTIM_DVO0_REG);
  pantim_dvo1 = ls_readl(dc_base + LS_FB_PANTIM_DVO1_REG);

  dvo0_hdisplay = ls_readl(dc_base + LS_FB_HDISPLAY_DVO0_REG);
  dvo1_hdisplay = ls_readl(dc_base + LS_FB_HDISPLAY_DVO1_REG);
  dvo0_vdisplay = ls_readl(dc_base + LS_FB_VDISPLAY_DVO0_REG);
  dvo1_vdisplay = ls_readl(dc_base + LS_FB_VDISPLAY_DVO1_REG);

  dvo0_hsync_reg = ls_readl(dc_base + LS_FB_HSYNC_DVO0_REG);
  dvo1_hsync_reg = ls_readl(dc_base + LS_FB_HSYNC_DVO1_REG);
  dvo0_vsync_reg = ls_readl(dc_base + LS_FB_VSYNC_DVO0_REG);
  dvo1_vsync_reg = ls_readl(dc_base + LS_FB_VSYNC_DVO1_REG);

  pr_info("dc_base:0x%lx, cfg_reg_dvo0:0x%x, cfg_reg_dvo1:0x%x\n", dc_base, cfg_reg_dvo0, cfg_reg_dvo1);
  pr_info("pitch_dvo0:0x%x, pitch_dvo1:0x%x\n", pitch_dvo0, pitch_dvo1);
  pr_info("dittab_lo_dvo0:0x%x, dittab_lo_dvo1:0x%x\n", dittab_lo_dvo0, dittab_lo_dvo1);
  pr_info("dittab_hi_dvo0:0x%x, dittab_hi_dvo1:0x%x\n", dittab_hi_dvo0, dittab_hi_dvo1);
  pr_info("pancfg_dvo0:0x%x, pancfg_dvo1:0x%x\n", pancfg_dvo0, pancfg_dvo1);
  pr_info("pantim_dvo0:0x%x, pantim_dvo1:0x%x\n", pantim_dvo0, pantim_dvo1);
  pr_info("dvo0_hr:0x%x, dvo1_hr:0x%x\n", dvo0_hdisplay, dvo1_hdisplay);
  pr_info("dvo0_vr:0x%x, dvo1_vr:0x%x\n", dvo0_vdisplay, dvo1_vdisplay);
  pr_info("dvo0_hsync:0x%x, dvo1_hsync:0x%x\n", dvo0_hsync_reg, dvo1_hsync_reg);
  pr_info("dvo0_vsync:0x%x, dvo1_vsync:0x%x\n", dvo0_vsync_reg, dvo1_vsync_reg);
  pr_info("%s return %ld\n", ri->rp->kp.symbol_name, retval);
  pr_info("-----------------------------------------\n\n");

  return 0;
}


static struct kretprobe kretp_ope = {
  .entry_handler = entry_handler_ope,
  .handler = ret_handler_ope,
  .data_size = 0,
  .maxactive = 20,
};


static struct kretprobe kretp_dmgc = {
  .entry_handler = entry_handler_dmgc,
  .handler = ret_handler_dmgc,
  .data_size = 0,
  .maxactive = 20,
};

static struct kretprobe kretp_dhpscm = {
  .entry_handler = entry_handler_dhpscm,
  .handler = ret_handler_dhpscm,
  .data_size = 0,
  .maxactive = 20,
};

static struct kretprobe kretp_lcd = {
  .entry_handler = entry_handler_lcd,
  .handler = ret_handler_lcd,
  .data_size = 0,
  .maxactive = 20,
};

static struct kretprobe kretp_loongson_get_modes = {
  .entry_handler = entry_handler_loongson_get_modes,
  .handler = ret_handler_loongson_get_modes,
  .data_size = 0,
  .maxactive = 20,
};

static struct kretprobe kretp_lcms = {
  .entry_handler = entry_handler_lcms,
  .handler = ret_handler_lcms,
  .data_size = 0,
  .maxactive = 20,
};

static int pc_module_init(void)
{
	int ret;

	pr_info("%s---%d\n", __func__, __LINE__);

  ///* 1. initial init */
  pls7a_rwlock = (rwlock_t *)kallsyms_lookup_name("ls7a_rwlock");
  if (!pls7a_rwlock) {
    pr_err("lookup ls7a_rwlock failed!\n");
    return -1;
  }

  pls7a_rwflags = (unsigned long *)kallsyms_lookup_name("ls7a_rwflags");
  if (!pls7a_rwflags) {
    pr_err("lookup ls7a_rwflags failed!\n");
    return -1;
  }

  /* 2, kprobe plant */
  /* 2.1, kretprobe plant */
  kretp_ope.kp.symbol_name = OPE;
  ret = register_kretprobe(&kretp_ope);
  if (ret < 0) {
    pr_err("register_kretprobe failed, returned %d\n", ret);
    return ret;
  }
  pr_info("Planted return probe at %s: %p\n",
    kretp_ope.kp.symbol_name, kretp_ope.kp.addr);

  kretp_dmgc.kp.symbol_name = DMGC;
  ret = register_kretprobe(&kretp_dmgc);
  if (ret < 0) {
    pr_err("register_kretprobe failed, returned %d\n", ret);
    return ret;
  }
  pr_info("Planted return probe at %s: %p\n",
    kretp_dmgc.kp.symbol_name, kretp_dmgc.kp.addr);

  kretp_dhpscm.kp.symbol_name = DHPSCM;
  ret = register_kretprobe(&kretp_dhpscm);
  if (ret < 0) {
    pr_err("register_kretprobe failed, returned %d\n", ret);
    return ret;
  }
  pr_info("Planted return probe at %s: %p\n",
    kretp_dhpscm.kp.symbol_name, kretp_dhpscm.kp.addr);

  kretp_lcd.kp.symbol_name = LCD;
  ret = register_kretprobe(&kretp_lcd);
  if (ret < 0) {
    pr_err("register_kretprobe failed, returned %d\n", ret);
    return ret;
  }
  pr_info("Planted return probe at %s: %p\n",
    kretp_lcd.kp.symbol_name, kretp_lcd.kp.addr);

  kretp_loongson_get_modes.kp.symbol_name = LOONGSON_GET_MODES;
  ret = register_kretprobe(&kretp_loongson_get_modes);
  if (ret < 0) {
    pr_err("register_kretprobe failed, returned %d\n", ret);
    return ret;
  }
  pr_info("Planted return probe at %s: %p\n",
    kretp_loongson_get_modes.kp.symbol_name, kretp_loongson_get_modes.kp.addr);

  kretp_lcms.kp.symbol_name = LCMS;
  ret = register_kretprobe(&kretp_lcms);
  if (ret < 0) {
    pr_err("register_kretprobe failed, returned %d\n", ret);
    return ret;
  }
  pr_info("Planted return probe at %s: %p\n",
    kretp_lcms.kp.symbol_name, kretp_lcms.kp.addr);


	return 0;
}

static void pc_module_exit(void)
{
	pr_info("%s---%d\n", __func__, __LINE__);

  unregister_kretprobe(&kretp_lcms);
  pr_info("kretprobe at %p unregistered\n", kretp_lcms.kp.addr);
  unregister_kretprobe(&kretp_loongson_get_modes);
  pr_info("kretprobe at %p unregistered\n", kretp_loongson_get_modes.kp.addr);
  unregister_kretprobe(&kretp_lcd);
  pr_info("kretprobe at %p unregistered\n", kretp_lcd.kp.addr);
  unregister_kretprobe(&kretp_dhpscm);
  pr_info("kretprobe at %p unregistered\n", kretp_dhpscm.kp.addr);
  unregister_kretprobe(&kretp_dmgc);
  pr_info("kretprobe at %p unregistered\n", kretp_dmgc.kp.addr);
  unregister_kretprobe(&kretp_ope);
  pr_info("kretprobe at %p unregistered\n", kretp_ope.kp.addr);

	return ;
}

module_init(pc_module_init);
module_exit(pc_module_exit);

MODULE_LICENSE("GPL");

